/**
 * file name  : Jenoware/it/ciano/jenoware/toPedigree.java
 * authors    : Luciano Xumerle
 * created    : mar 27 nov 2007 17:09:14 CET
 *
 * Copyright (c) 2004-2005 Luciano Xumerle. All rights reserved.
 *
* This file is part of gephinfo.
*
* gephinfo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.

 *
 */

package it.ciano.jenoware;

import java.util.ArrayList;
import it.ciano.util.CFile;
import it.ciano.util.CString;
import java.util.regex.Pattern;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

/**
 * A collection of static methods to read several filetypes.
 *
 * @author Luciano Xumerle
 * @version 0.0.1
 */
final public class toPedigree {

    /**
     * Empty Constructor.
     */
    public toPedigree() {}

    /**
     * Imports into a Pedigree the Genotypes stored in CSV file.
     * The first line of the input file is skipped (it is the header).
     * Each input line processed has the format: "id;marker-name;allele1;allele2".
     *
     * @param ped desination Pedigree object.
     * @param args the filename.
     * @param sep the separator used in source file.
     */
    public static void importID_marker_allele_allele (  Pedigree ped, String[] args, String separator )
    throws java.io.IOException {
        String sep = manageSeparator ( separator );
        System.out.print ( "   - Processing \"id;marker-name;allele1;allele2\" files.\n" );
        // processo i file dati in input
        for ( int f=0; f<args.length; f++ ) {
            // leggo il file in input
            ArrayList ff=CFile.readTXT ( args[f] );

            for ( int i=1; i<ff.size(); i++ ) {
                String[] field = ( ( String ) ff.get ( i ) ).trim().split ( sep );
                if ( ped.getSBJ ( field[0], field[0]  ) == null )
                    ped.addSbj ( new Sbj ( field[0] ) );
                ped.addMarker2List ( field[1] );
                ped.getSBJ ( field[0],field[0]  ).addGenotype ( field[1],field[2], field[3] );
            }
        }
    }

    /**
     * Populates the Pedigree object with data from a CSV file.
     * The CSV file has the ';' as separator and contains id and SNPs only.
     * Each SNP genotype is written as a number from the set [1,2,3].
     * The number will be converted as a pair of alleles as follow:
     * <i>1 -&gt; 1-1</i>; <i>2 -&gt; 1-2</i>; <i>3 -&gt; 2-2</i>.
     *
     * @param ped the pedigree object.
     * @param files list of file to import.
     * @param sep the separator of CSV file.
     */
    public static void importID_snps ( Pedigree ped, String[] files, String separator )
    throws java.io.IOException {
        String sep = manageSeparator ( separator );
        PairAllele[] pairs = new PairAllele[] {
            new PairAllele( ),
            new PairAllele ( 1, 1 ),
            new PairAllele ( 1, 2 ),
            new PairAllele ( 2, 2 )
        };

        // add the CSV files to the Pedigree
        for ( int i = 0; i < files.length; i++ )
            importCSV ( ped, files[ i ], sep );
        // get ID list
        Sbj[] myID = ped.getListSbj();
        // genotype stored as a single column are detected as phenotype
        String[] phnt = ped.getPhenotypesList();
        if ( phnt != null ) {
            // if exist some phenotypes the process all the Sbj
            for ( int i = 0; i < myID.length; i++ )
                for ( int j = 0; j < phnt.length; j++ ) {
                    // add the new marker to the marker list
                    ped.addMarker2List ( phnt[ j ] );
                    // get the marker position inside the PairAllele array
                    int position = CString.getInteger ( myID[ i ].getPhenotype ( phnt[ j ] ).getValue(), 0, pairs.length - 1 );
                    // create the corresponding Genotype
                    Genotype geno = new Genotype ( phnt[ j ], position, pairs, "" );
                    // add the Genotype to the current Sbj
                    myID[ i ].addGenotype ( geno );
                }
        }
    }


    /**
     * Adds the values contained in the given CSV file.
     *
     * @param csvFile the name of the CSV file.
     * @param separator the CSV file separator.
     * @return true or false.
     */
    public static boolean importCSV ( Pedigree ped, String csvFile, String separator ) {
        String sep=manageSeparator ( separator );
        String text;
        boolean isok = false;
        try {
            // open file & get the header
            BufferedReader buf = new BufferedReader ( new FileReader ( new File ( csvFile ) ) );
            CsvHead head;
            if ( ( text = buf.readLine() ) != null ) {
                if ( text.charAt ( 0 ) == '!' )
                    text = text.substring ( 1, text.length() );
                head = new CsvHead ( text.split ( sep, -1 ) );
            } else
                return isok;
            while ( ( text = buf.readLine() ) != null ) {
                if ( text.equals ( "" ) )
                    continue;
                isok = true;
                head.addRow ( ped, text.split ( sep, -1 ) );
            }
            return isok;
        } catch ( IOException ex ) {
            System.err.println ( "Error: CSV " + csvFile + " has not been added." );
        }
        return isok;
    }


    /**
     * Populates the Pedigree object with data from an IXO file generated by the LightCycler.
     *
     * @param ped the pedigree object.
     * @param args list of files to import.
     */
    public static void importIXO ( Pedigree ped, String[] args )
    throws java.io.IOException {
        // processo gli ixo file dati in input
        for ( int f=0; f<args.length; f++ ) {

            System.out.print ( "   - Processing ixo file: " + args[f] + " ... " );

            // leggo i file ixo in input
            ArrayList ixo=CFile.readTXT ( args[f] );

            ArrayList idList=new ArrayList();
            String marker=null;
            Phenotype genotypeClass=null;
            int counter=0;

            for ( int i=0; i<ixo.size(); i++ ) {
                String line= ( ( String ) ixo.get ( i ) ).trim();

                // detect IDs
                if ( line.indexOf (   "class=\"LCSample\"" ) >-1    )
                    idList.add ( new Sbj (  purgeXMLtag ( ( String ) ixo.get ( i+1 )  ) ) );
                //  if (  line.indexOf ( "TQualDetectSample" ) >-1  )
                // System.out.println( purgeXMLtag ( ( String )  ixo.get ( i+1 ) ) );

                // Detect markers name
                if (  line.indexOf ( "class=\"Qual_Mut_Detection\"" ) >-1   ) {
                    marker=   purgeXMLtag ( ( String ) ixo.get ( i+1 ) );
                    ped.addPhenotype2List ( marker );
                }

                // detect genotype class of the marker
                if ( marker!=null &&  line.indexOf ( "class=\"TMDGroups\"" ) >-1  ) {
                    line= ( String )  ixo.get ( i+1 );
                    if ( line.indexOf ( "prop name=\"name\"" ) >-1 )
                        genotypeClass=new Phenotype ( marker, purgeXMLtag ( line ) );
                    line= ( ( String ) ixo.get ( i+5 ) );
                    System.err.println ( line.substring ( line.indexOf ( "count=\"" ) +7, line.indexOf ( "\">" ) ) );
                    counter=CString.getInteger ( line.substring ( line.indexOf ( "count=\"" ) +7  , line.indexOf ( "\">" )     )  ).intValue();
                    System.err.println (   counter + "  "  +  line.substring (  line.length() -3 ,line.length() -2 ) );
                }

                // assign genotype to subjects
                if (  genotypeClass!=null
                        && counter>0
                        &&  line.indexOf ( "name=\"SampleNumber\"" ) >-1    ) {
                    int index=CString.getInteger (  purgeXMLtag ( line )  ).intValue();
                    Sbj temp= ( Sbj ) idList.get ( index );
                    if ( !temp.getPhenotype ( marker ).isValid() || true ) {
                        ( ( Sbj ) idList.get ( index ) ).addPhenotype (  genotypeClass  );
                        counter--;
                    }
                }
            }
            // update csv
            for ( int i=0; i<idList.size(); i++ )
                ped.addSbj ( ( Sbj ) idList.get ( i )  );

            System.out.println ( "DONE!" );
        }
    }


    /**
     * Imports the results file produced by the program hapsim.
     * The methods stores every Sbj and retrieves the distance between markers.
     *
     * @param file the name of one hapsim result file.
     * @return the array containing the distance between markers.
     */
    public static int[] importHapsim ( Pedigree ped, String file ) {
        final String numberLociLine = "number\\s+of\\s+loci\\s*";
        int numberLoci = 0;
        int counter = 1;
        int distances[] = null;
        ArrayList hapsim;
        // import file to an ArrayList
        hapsim = CFile.readTXT ( file );
        // parse imported hapsim file
        for ( int i = 0; i < hapsim.size(); i++ ) {
            // retrieve number of generated loci
            if ( numberLoci == 0 && Pattern.compile ( numberLociLine ).matcher ( ( String ) hapsim.get ( i ) ).find() ) {
                String num = Pattern.compile ( numberLociLine ).matcher ( ( String ) hapsim.get ( i ) ).replaceFirst ( "" );
                numberLoci = CString.getInteger ( num.trim() ).intValue();
                for ( int g = 1; g <= numberLoci; g++ )
                    ped.addMarker2List ( "geno" + g );
                ped.addPhenotype2List ( "case-control" );
            }
            // retrieve the  recombinations freq and produces the distance
            else if ( Pattern.compile ( "recomb theta" ).matcher ( ( String ) hapsim.get ( i ) ).find() ) {
                /*
                 * la frequenza di ricombinazione è equivalente alla distanza tra
                 * i marcatori quando le distanze sono piccole.
                 * la frequenza di 0.1 equivale ad una distanza di 0.1 Morgan
                 * che sono 10centiMorgan che sono 10*10^7 basi
                 */
                String[] recombinations = Pattern.compile ( "\\s+" ).split ( ( ( String ) hapsim.get ( i + 1 ) ).trim() );
                distances = new int[ recombinations.length ];
                for ( int j = 0; j < recombinations.length; j++ ) {
                    float ll = CString.getFloat ( recombinations[ j ] ).floatValue();
                    distances[ j ] = ( int ) Math.round ( ll * 100000000 );
                }
            }
            // search for Sbj
            else {
                String[] fields = Pattern.compile ( "\\s+" ).split ( ( ( String ) hapsim.get ( i ) ).trim() );
                // insert Sbj into Pedigree object
                if ( numberLoci > 0 && fields.length == numberLoci * 2 + 1 ) {
                    String id = counter + "";
                    Sbj ss = new Sbj ( id );
                    ss.addPhenotype ( "case-control", fields[ 0 ] );
                    // first value = 1 set a case (default) and value 0 set a control
                    if ( fields[ 0 ].equals ( "0" ) )
                        ss.setAsControl();
                    for ( int g = 1; g <= numberLoci; g++ )
                        ss.addGenotype ( new Genotype ( "geno" + g ,
                                                        CString.getInteger ( fields[ g ] ).intValue() + 1 ,
                                                        CString.getInteger ( fields[ g + numberLoci ] ).intValue() + 1 ) );
                    ped.addSbj ( ss );
                    counter++;
                }
            }
        }
        return distances;
    }


    /**
     * Returns the string purged by XML tags.
     *
     * @param line An XML string.
     * @return cleaned string.
     */
    private static String purgeXMLtag ( String line ) {
        return line.substring ( line.indexOf ( ">" ) +1 , line.lastIndexOf ( "<" )  );
    }


    /**
     * Imports into the given Pedigree object the list of pairs files produced by PHASE.
     *
     * @param ped the pedigree object.
     * @param file the file to import.
     * @param haploName the name to assign to the haplotype.
     * @param mantainAlleleOrder mantain the order of allele stored in the file.
     * @throws java.io.IOException
     */
    public static void importPHASEpairs ( Pedigree ped, String file, String haploName, boolean mantainAlleleOrder )
    throws java.io.IOException {
        // leggo i file ixo in input
        ArrayList pairs=CFile.readTXT ( file );
        Sbj curID=null;
        ped.addMarker2List ( haploName );
        for ( int p=0; p<pairs.size(); p++ ) {
            String line= ( ( String ) pairs.get ( p ) ).trim();
            if ( line.indexOf ( "IND:" ) >-1 ) {
                if ( curID!=null )
                    ped.addSbj ( curID );
                curID=new Sbj ( line.substring ( line.indexOf ( " " ) ).trim() );
            } else {
                if ( line.indexOf ( "," ) <0 )
                    continue;
                String[] hh=line.split ( "\\s+,\\s+",3 );
                Allele a1=new Allele ( hh[0] );
                Allele a2=new Allele ( hh[1] );
                double freq=getDouble ( hh[2], 1 );

                if ( curID.getGenotype ( haploName ).isValid() )
                    curID.getGenotype ( haploName ).addPairAllele
                    ( new PairAllele ( a1, a2, freq, mantainAlleleOrder ) );
                else
                    curID.addGenotype
                    ( new Genotype ( haploName, a1, a2, "", freq, mantainAlleleOrder ) );
            }
        }
    }

    /**
     * Returns the double value rapresented by the given String.
     *
     * @param num the string number.
     * @return the double value rapresented by the given String.
     */
    private static double getDouble ( String num, double defaultValue ) {
        try {
            return Double.valueOf ( num.trim() ).doubleValue();
        } catch ( NumberFormatException nfe ) {
            return defaultValue;
        }
    }


    /**
     * Sets the haplo Pedigree object with only a marker. The marker is the
     * Haplotype given concatenating each marker present in the source Pedigree
     * object.
     *
     * @param ped input Pedigree.
     * @param haplo destination Pedigree.
     * @param name name of the results Haplotype marker.
     * @param type id one of: c (concatenation) - g (gerbil) - s (snpHAP) - h (HIT) - H (HAP).
     */
    public static void toHaplotypeMarker ( Pedigree ped, Pedigree haplo, String name, char type ) {
        haplo.addMarker2List ( name );
        String[] markers=ped.getMarkersList();
        Sbj[] sbjs=ped.getListSbj();

        String[][]  destAllele= {{"?","?","?"},{"?","?","?"}};
        if ( type=='g' || type=='h' || type=='H' || type=='s' )
            destAllele=getHapAlleleList ( ped, markers );

        for ( int i=0; i<sbjs.length; i++ ) {
            PairAllele[] aa=new PairAllele[markers.length];
            for ( int g=0; g<markers.length; g++ )
                aa[g]=sbjs[i].getGenotype ( markers[g] ).getPairAllele ( 0 );
            Haplotype hh=new Haplotype ( aa );

            // set the allele type
            String[] alleles;
            if ( type=='g' )
                alleles=hh.toGerbil ( destAllele );
            else if ( type=='h' )
                alleles=hh.toHIT ( destAllele );
            else if ( type=='s' ) {
                String all=hh.toSNPhap ( destAllele );
                alleles=new String[] {all, all};
            } else if ( type=='H' ) {
                String all=hh.toHAP ( destAllele );
                alleles=new String[] {all, all};
            } else {
                PairAllele pp=hh.toPairAllele();
                alleles=new String[] { pp.getAllele ( 1 ).toString(), pp.getAllele ( 2 ).toString() };
            }

            if ( haplo.getSBJ ( sbjs[i].getID() ) == null ) {
                Sbj ss=new  Sbj ( sbjs[i].getID(), "",
                                  "", "", "", sbjs[i].getSEX(), "" );
                haplo.addSbj ( ss );
            }
            haplo.getSBJ ( sbjs[i].getID() ).addGenotype
            ( new Genotype ( name, alleles[0], alleles[1] ) );
        }
    }

    /**
     * Returns a matrix. Each line contains 3 elements:
     * the first SNP allele and the first and second label used to create
     * the HAP file.
     *
     * @param ped the pedigree.
     * @param markers the markers list.
     * @return the matrix.
     */
    private static String[][] getHapAlleleList ( Pedigree ped, String[] markers ) {
        String[][] res=new String[markers.length][3];
        for ( int i=0; i<markers.length; i++ ) {
            MarkerFreq temp=new MarkerFreq ( ped, markers[i] );
            Allele[] aa=temp.getOrderedAlleleList();
            if ( aa.length == 0 )
                aa = new Allele[] { new Allele(), new Allele()  };
            res[i][0]=aa[0].toString();
            res[i][1]=aa[0].toString().toUpperCase();
            if ( aa.length==2 )
                res[i][2]=aa[1].toString().toUpperCase();
            else
                res[i][2]="N";

            if ( !res[i][1].equals ( "A" ) && !res[i][1].equals ( "C" )
                    && !res[i][1].equals ( "G" ) && !res[i][1].equals ( "T" ) )
                res[i][1]="A";

            if ( !res[i][2].equals ( "A" ) && !res[i][2].equals ( "C" )
                    && !res[i][2].equals ( "G" ) && !res[i][2].equals ( "T" ) )
                if ( res[i][1].equals ( "T" ) )
                    res[i][2]="A";
                else
                    res[i][2]="T";
        }
        return res;
    }

    private static String manageSeparator ( String in ) {
        if ( in.equals ( "t" ) ) {
            return "\t";
        }
        return in;
    }

} // end class

